/*
 Copyright (c) 2013- MeherTJ G.
 All rights reserved. Everybody can use these code freely.
 */

#pragma once

#include <sstream>
#include <string>
#include <vector>
#include <deque>
#include <cstdio>

namespace Zen
{
	namespace Hex
	{
		/// Hex
		inline char ValueToDigit(int v16, bool caps = true)
		{
			return (v16 > 15 ? 0 : (v16 < 10 ? (char)('0' + v16) : (v16 < 0 ? 0 : (char)((caps ? 'A' : 'a') + (v16 - 10)))));
		}

		inline int DigitToValue(char hex)
		{
			return (hex < '0' ? -1 : (hex <= '9' ? hex - '0' : (hex < 'A' ? -1 : (hex <= 'F' ? hex - 'A' + 10 : (hex < 'a' ? -1 : (hex <= 'f' ? hex - 'a' + 10 : -1))))));
		};

		/// @pragma min_len: if the valid width less that min_len, prefix 0 added.
		inline std::string HexStringFromNumber(uint64_t value, size_t min_len = 0)
		{
			std::string res;
			while (value)
			{
				auto r = (uint8_t)(value & 0xf);
				res.push_back(ValueToDigit(r));
				value >>= 4;
			}
			if (min_len == 0)
				min_len = res.size();
			else if (min_len > res.size())
				res.resize(min_len, '0');

			for (auto i = res.begin(), e = res.end() - 1; i < e; ++i, --e)
			{
				std::swap(*i, *e);
			}
			return res;
		}

		inline uint64_t HexStringToNumber(std::string const& hex)
		{
			uint64_t value = 0;
			for (auto i : hex)
			{
				auto v = DigitToValue(i);
				if (v == -1) break;
				value <<= 4;
				value |= v;
			}
			return value;
		}

		inline std::string HexEncode(void const* value, size_t size)
		{
			std::string res;
			res.reserve(size << 1);

			auto buf = (uint8_t const*)value;
			for (size_t i = 0; i < size; ++i)
			{
				res.push_back(Zen::Hex::ValueToDigit(buf[i] >> 4));
				res.push_back(Zen::Hex::ValueToDigit(buf[i] & 0xf));
			}
			return res;
		}

		/**
	 "abc" => "616263"
	 */
		template <typename _Char>
		inline std::string HexEncode(std::vector<_Char> const& value)
		{
			static_assert(sizeof(_Char) == 1, "only char allowed here");
			return HexEncode(value.data(), value.size());
		}

		inline std::vector<uint8_t> HexDecode(std::string const& s)
		{
			std::vector<uint8_t> value;
			value.reserve(s.size() >> 1);

			for (decltype(s.size()) i = 1; i < s.size(); i += 2)
			{
				int v = (Zen::Hex::DigitToValue(s[i - 1]) << 4) | Zen::Hex::DigitToValue(s[i]);
				value.push_back((uint8_t)v);
			}
			return value;
		}
	} // namespace Hex
} // namespace Zen
